package gps;

import gps.api.GPSProblem;
import gps.api.GPSRule;
import gps.api.GPSState;
import gps.exception.NotAppliableException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public abstract class GPSEngine {

	protected List<GPSNode> open = new LinkedList<GPSNode>();

	protected List<GPSNode> closed = new ArrayList<GPSNode>();
	
	protected List<GPSNode> recentlyExploded;

	protected GPSProblem problem;

	// Use this variable in the addNode implementation
	protected SearchStrategy strategy;
	
	private long explosionCounter;
	private long explosionCounterNotReseted;

	public void engine(GPSProblem myProblem, SearchStrategy myStrategy) {

		problem = myProblem;
		strategy = myStrategy;

		GPSNode rootNode = new GPSNode(problem.getInitState(), 0);
		boolean finished = false;
		boolean failed = false;
		long explosionCounter = 0;
		long explosionCounterNotReseted = 0;

		open.add(rootNode);
		int count = 0;
		while (!failed && !finished) {
			if (open.size() <= 0) {
				failed = true;
			} else {
				count++;
				GPSNode currentNode = open.get(0);
				closed.add(currentNode);
				open.remove(0);
				if (isGoal(currentNode)) {
					finished = true;
					System.out.println("Expanded nodes: " + explosionCounter);
					this.explosionCounter = explosionCounter;
					System.out.println("Expanded nodes without reset of counter for SSID: " + explosionCounterNotReseted);
					this.explosionCounterNotReseted = explosionCounterNotReseted;
				} else {
					explosionCounter++;
					explosionCounterNotReseted++;
					explode(currentNode);
				}
			}
			/* 
			 * ID search strategy condition
			 */
			if (open.size() == 0 && strategy.equals(SearchStrategy.ID)) {
				clearClosed();
				incIDLevel();
				failed = false;
				explosionCounter = 0;
				open.add(new GPSNode(problem.getInitState(), 0));
			}
		}

		if (finished) {
			System.out.println("OK! solution found!");
		} else if (failed) {
			System.err.println("FAILED! solution not found!");
		}
	}

	private boolean isGoal(GPSNode currentNode) {
		return currentNode.getState().isGoal();
	}

	protected boolean explode(GPSNode node) {
		if(strategy.equals(SearchStrategy.ID) && node.getDepth()>=getIDLevel()){
			return false;
		}
		List<GPSRule> rules = problem.getRules();
		if (rules == null) {
			System.err.println("No rules!");
			return false;
		}
		if(strategy.equals(SearchStrategy.DFS) ||strategy.equals(SearchStrategy.ID)){
			Collections.reverse(rules);
		}
		recentlyExploded = new LinkedList<GPSNode>();
		for (GPSRule rule : rules) {
			GPSState newState = null;
			try {
				newState = rule.evalRule(node.getState());
			} catch (NotAppliableException e) {
			}
			if (newState != null
					&& !checkBranch(node, newState)
					&& !checkOpenAndClosed(node.getCost() + rule.getCost(),
							newState)) {
				GPSNode newNode = new GPSNode(newState, node.getCost()
						+ rule.getCost());
				newNode.setParent(node);
				recentlyExploded.add(newNode);
				addNode(newNode);
			}
		}
		return true;
	}

	private boolean checkOpenAndClosed(Integer cost, GPSState state) {
		for (GPSNode openNode : open) {
			if (openNode.getState().compare(state) && openNode.getCost() <= cost) {
				return true;
			}
		}
		for (GPSNode closedNode : closed) {
			if (closedNode.getState().compare(state)
					&& closedNode.getCost() <= cost) {
				return true;
			}
		}
		return false;
	}

	private boolean checkBranch(GPSNode parent, GPSState state) {
		if (parent == null) {
			return false;
		}
		return checkBranch(parent.getParent(), state)
				|| state.compare(parent.getState());
	}

	public void clearOpen() {
		open = new LinkedList<GPSNode>();
	}

	public void clearClosed() {
		closed = new ArrayList<GPSNode>();
	}

	public abstract void addNode(GPSNode node);

	public abstract void incIDLevel();

	public abstract int getIDLevel();

	public long getExplosionCounter(){
		return explosionCounter;
	}
	
	public long getExplosionCounterNotReseted(){
		return explosionCounterNotReseted;
	}
}
